package com.ucode.hrm.service.impl;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.ucode.hrm.mapper.HrmAttendanceRecordMapper;
import com.ucode.hrm.service.HrmAttendanceRecordService;
import com.ucode.model.hrm.HrmAttendanceConfig;
import com.ucode.model.hrm.HrmAttendanceRecord;
import com.ucode.model.hrm.enums.HrmAttendanceRecordsStatusEnum;
import com.ucode.model.hrm.enums.HrmClockType;
import com.ucode.tool.base.enums.GlobalResultCodeEnum;
import com.ucode.tool.exception.UcodeServiceException;
import com.ucode.tool.util.IdentifyUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Optional;

@Service
public class HrmAttendanceRecordServiceImpl implements HrmAttendanceRecordService {

    // 地球半径（单位：米）
    private static final double EARTH_RADIUS = 6371000;

    private static Log log = LogFactory.get();

    @Resource
    private HrmAttendanceRecordMapper hrmAttendanceRecordMapper;

    @Override
    public HrmAttendanceRecordMapper initMapper() {
        return hrmAttendanceRecordMapper;
    }

    @Override
    public Long insert(HrmAttendanceRecord bean) {
        if(ObjUtil.isNull(bean.getId()))
            bean.setId(IdentifyUtils.getDefaultSnowflakeId());
        int i = hrmAttendanceRecordMapper.insert(bean);
        return i > 0 ? bean.getId() : null;
    }

    @Override
    public Long processAttendance(HrmAttendanceConfig config, Long employeeId,Long companyId,Long projectId,LocalDate workDate, HrmClockType clockType, BigDecimal lat, BigDecimal lng, String location,List<String> pics, String remark) {
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        LocalDate today = now.toLocalDate();
        LocalTime totime = now.toLocalTime();

        // 根据打卡类型和时间判断实际的考勤日期(修改为客户端传递过来，后端无法控制回头更新打卡的情况)
        //LocalDate workDate = determineWorkDate(today, totime, clockType, config);

        HrmAttendanceRecord record = this.getEmployeeAttendanceRecord(employeeId, workDate);
        if(ObjUtil.isNull( record)){
            record = new HrmAttendanceRecord();
        }
        record.setEmployeeId(employeeId);
        record.setCompanyId(companyId);
        record.setProjectId(projectId);
        record.setWorkDate(workDate);

        // 检查打卡类型是否合法
        if(ObjUtil.equals(clockType, HrmClockType.CLOCK_IN)) {
            // 如果是跨天场景下且当前时间属于前一天的下班时间段，则不允许打上班卡
            //if (isCrossDayClockOutTime(config, totime)) {
            //    throw new UcodeServiceException(GlobalResultCodeEnum.VALIDATE_FAILED.getCode(), "当前时间段只能打下班卡");
            //}

            if (config.getClockInEndTime() != null && config.getClockInEndTime().isBefore(totime)){
                throw new UcodeServiceException(GlobalResultCodeEnum.VALIDATE_FAILED.getCode(), "上班打卡时间已过");
            }
        }

        if(ObjUtil.equals(clockType, HrmClockType.CLOCK_OUT) && config.getClockOutEndTime() != null){
            // 处理下班打卡时间跨天的情况
            LocalTime clockOutEndTime = config.getClockOutEndTime();
            // 如果下班打卡截止时间早于下班时间，说明是跨天的情况
            if (config.getClockOutTime() != null && clockOutEndTime.isBefore(config.getClockOutTime())) {
                // 跨天情况：下班打卡截止时间在第二天
                // 员工可以在下班时间之后打卡下班，直到下班打卡截止时间
                // 员工也可以在下班时间之前打卡下班（早退）
                // 所以在跨天情况下，基本上不限制员工打卡下班
                
                // 在跨天情况下，判断是否超过下班打卡截止时间需要特殊处理
                // 但由于涉及跨天，直接比较LocalTime无法准确判断
                // 简化处理：在跨天情况下，允许员工在合理范围内打卡下班
            } else {
                // 不跨天情况：当前时间不能晚于下班打卡截止时间
                if (totime.isAfter(clockOutEndTime)) {
                    throw new UcodeServiceException(GlobalResultCodeEnum.VALIDATE_FAILED.getCode(), "下班打卡时间已过");
                }
                // 员工可以在下班时间之前打卡下班（早退），所以不需要检查是否早于下班时间
            }
        }


        // 计算当前打卡位置与考勤点的距离
        double distance = calculateDistance(
                config.getLocationLat().doubleValue(),
                config.getLocationLng().doubleValue(),
                lat.doubleValue(),
                lng.doubleValue()
        );

        // 判断是否在允许范围内
        boolean isInRange = distance <= config.getAllowRadius();
        // 处理不同类型的打卡
        switch (clockType) {
            case CLOCK_IN:
                record.setClockInRemark(remark);
                record.setClockInPics(pics);
                processClockIn(config, record, now, lat, lng, location, remark, distance, isInRange);
                break;
            case CLOCK_OUT:
                record.setClockOutRemark(remark);
                record.setClockOutPics(pics);
                processClockOut(config, record, now, lat, lng, location, remark, distance, isInRange);
                break;
        }

        // 更新整体考勤状态
        updateAttendanceStatus(record);

        if(ObjUtil.isNull(record.getId())){
            record.setId(IdentifyUtils.getDefaultSnowflakeId());
            hrmAttendanceRecordMapper.insert(record);
        }else {
            hrmAttendanceRecordMapper.update(record);
        }

        return record.getId();
    }

    @Override
    public Long reissueAttendance(HrmAttendanceConfig config, Long employeeId, Long companyId, Long projectId, HrmClockType clockType, LocalDate workDate) {
        HrmAttendanceRecord record = this.getEmployeeAttendanceRecord(employeeId, workDate);
        if(ObjUtil.isNull( record)){
            record = new HrmAttendanceRecord();
        }
        record.setEmployeeId(employeeId);
        record.setCompanyId(companyId);
        record.setProjectId(projectId);
        record.setWorkDate(workDate);

        // 计算当前打卡位置与考勤点的距离
        double distance = calculateDistance(
                config.getLocationLat().doubleValue(),
                config.getLocationLng().doubleValue(),
                config.getLocationLat().doubleValue(),
                config.getLocationLng().doubleValue()
        );

        // 判断是否在允许范围内
        boolean isInRange = distance <= config.getAllowRadius();


        // 处理不同类型的打卡
        switch (clockType) {
            case CLOCK_IN:
                LocalDateTime reissueTime = config.getClockInTime().atDate(workDate);
                processClockIn(config, record, reissueTime, config.getLocationLat(), config.getLocationLng(), config.getLocation(), "", distance, isInRange);
                break;
            case CLOCK_OUT:
                LocalDateTime reissueTime2 = config.getClockOutTime().atDate(workDate);
                processClockOut(config, record, reissueTime2, config.getLocationLat(), config.getLocationLng(), config.getLocation(), "", distance, isInRange);
                break;
        }

        // 更新整体考勤状态
        updateAttendanceStatus(record);

        if(ObjUtil.isNull(record.getId())){
            record.setId(IdentifyUtils.getDefaultSnowflakeId());
            hrmAttendanceRecordMapper.insert(record);
        }else {
            hrmAttendanceRecordMapper.update(record);
        }

        return record.getId();
    }

    /**
     * 处理上班打卡
     */
    private void processClockIn(
            HrmAttendanceConfig config,
            HrmAttendanceRecord record,
            LocalDateTime now,
            BigDecimal lat,
            BigDecimal lng,
            String location,
            String remark,
            double distance,
            boolean isInRange) {

        // 设置上班打卡信息
        record.setClockInTime(now);
        record.setClockInLat(lat);
        record.setClockInLng(lng);
        record.setClockInLocation(location);
        record.setClockInRemark(remark);
        record.setClockInInRange(isInRange);

        // 计算迟到时长（分钟）
        LocalTime configStartTime = config.getClockInTime();
        LocalTime actualTime = now.toLocalTime();

        // 考虑迟到阈值
        LocalTime lateThresholdTime = configStartTime.plusMinutes(config.getLateThreshold());

        // 判断是否迟到
        if (actualTime.isAfter(lateThresholdTime)) {
            // 计算迟到分钟数
            long lateMinutes = Duration.between(configStartTime, actualTime).toMinutes();
            record.setLateDuration((int) lateMinutes); // 记录迟到时长
        } else {
            record.setLateDuration(0);// 无迟到
        }
    }

    /**
     * 处理下班打卡
     */
    private void processClockOut(
            HrmAttendanceConfig config,
            HrmAttendanceRecord record,
            LocalDateTime now,
            BigDecimal lat,
            BigDecimal lng,
            String location,
            String remark,
            double distance,
            boolean isInRange) {

        // 设置下班打卡信息
        record.setClockOutTime(now);
        record.setClockOutLat(lat);
        record.setClockOutLng(lng);
        record.setClockOutLocation(location);
        record.setClockOutRemark(remark);
        record.setClockOutInRange(isInRange);

        // 计算早退时长（分钟）
        LocalTime configEndTime = config.getClockOutTime();
        LocalTime actualTime = now.toLocalTime();

        // 考虑早退阈值
        LocalTime earlyThresholdTime = configEndTime.minusMinutes(config.getEarlyLeaveThreshold());

        // 判断是否早退
        if (actualTime.isBefore(earlyThresholdTime)) {
            // 计算早退分钟数
            long earlyMinutes = Duration.between(actualTime, configEndTime).toMinutes();
            record.setEarlyLeaveDuration((int) earlyMinutes);
        } else {
            record.setEarlyLeaveDuration(0);// 无早退
        }
    }

    /**
     * 判断当前时间是否属于前一天的下班打卡时间段（跨天场景）
     *
     * @param config 考勤配置
     * @param currentTime 当前时间
     * @return 是否属于前一天的下班打卡时间段
     */
    private boolean isCrossDayClockOutTime(HrmAttendanceConfig config, LocalTime currentTime) {
        // 判断是否存在跨天场景：下班打卡截止时间早于下班时间
        if (config.getClockOutTime() != null && config.getClockOutEndTime() != null
                && config.getClockOutEndTime().isBefore(config.getClockOutTime())) {
            // 如果当前时间在00:00到下班打卡截止时间之间，说明属于前一天的下班时间段
            if (currentTime.isAfter(LocalTime.MIDNIGHT) && currentTime.isBefore(config.getClockOutEndTime())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据打卡时间和类型确定考勤日期
     *
     * @param currentDate 当前日期
     * @param currentTime 当前时间
     * @param clockType 打卡类型
     * @param config 考勤配置
     * @return 正确的考勤日期
     */
    private LocalDate determineWorkDate(LocalDate currentDate, LocalTime currentTime, HrmClockType clockType, HrmAttendanceConfig config) {
        // 如果是下班打卡，需要考虑跨天的情况
        if (ObjUtil.equals(clockType, HrmClockType.CLOCK_OUT) && config.getClockOutTime() != null && config.getClockOutEndTime() != null) {
            LocalTime clockOutTime = config.getClockOutTime();
            LocalTime clockOutEndTime = config.getClockOutEndTime();

            // 如果下班打卡截止时间早于下班时间，说明是跨天的情况
            if (clockOutEndTime.isBefore(clockOutTime)) {
                // 跨天情况：下班打卡截止时间在第二天
                // 如果当前时间在00:00到下班打卡截止时间之间，则属于前一天的考勤
                if (currentTime.isAfter(LocalTime.MIDNIGHT) && currentTime.isBefore(clockOutEndTime)) {
                    return currentDate.minusDays(1);
                }
            }
        }
        // 其他情况直接使用当前日期
        return currentDate;
    }

    /**
     * 更新整体考勤状态
     */
    private void updateAttendanceStatus(HrmAttendanceRecord record) {
        // 状态判断逻辑
        boolean hasClockIn = record.getClockInTime() != null;
        boolean hasClockOut = record.getClockOutTime() != null;

        //全天缺勤
        if (!hasClockIn && !hasClockOut) {
            record.setStatuz(HrmAttendanceRecordsStatusEnum.ABSENT.getCode());
            return;
        }

        //缺下班卡
        if (hasClockIn && !hasClockOut) {
            record.setStatuz(HrmAttendanceRecordsStatusEnum.OUT_ABSENT.getCode());
            return;
        }

        //缺上班卡
        if (!hasClockIn && hasClockOut) {
            record.setStatuz(HrmAttendanceRecordsStatusEnum.IN_ABSENT.getCode());
            return;
        }

        // 正常打卡情况下的状态判断
        boolean isLate = Optional.ofNullable(record.getLateDuration()).orElse(0) > 0;
        boolean isEarlyLeave = Optional.ofNullable(record.getEarlyLeaveDuration()).orElse(0) > 0;
        boolean inRangeClockIn = Optional.ofNullable(record.getClockInInRange()).orElse(false);
        boolean inRangeClockOut = Optional.ofNullable(record.getClockOutInRange()).orElse(false);

        //组合状态判断：根据迟到、早退、打卡位置范围等条件综合判定考勤状态
        if (isLate && isEarlyLeave) {
            // 情况1：同时满足迟到和早退条件
            // 设置状态为"迟到且早退"的枚举值
            record.setStatuz(HrmAttendanceRecordsStatusEnum.LATE_AND_EARLY_LEAVE.getCode());
        } else if (isLate) {
            // 情况2：仅满足迟到条件（不满足早退）
            // 设置状态为"迟到"的枚举值
            record.setStatuz(HrmAttendanceRecordsStatusEnum.LATE.getCode());
        } else if (isEarlyLeave) {
            // 情况3：仅满足早退条件（不满足迟到）
            // 设置状态为"早退"的枚举值
            record.setStatuz(HrmAttendanceRecordsStatusEnum.EARLY_LEAVE.getCode());
        } else if (!inRangeClockIn || !inRangeClockOut) {
            // 情况4：无迟到早退，但上班或下班打卡位置超出允许范围
            // 注意：使用||逻辑或，表示任一打卡位置超出范围即视为异常
            // 设置状态为"超出范围"的枚举值
            record.setStatuz(HrmAttendanceRecordsStatusEnum.OUT_OF_RANGE.getCode());
        } else {
            record.setStatuz(HrmAttendanceRecordsStatusEnum.NORMAL.getCode());
        }
    }


    /**
     * 使用Haversine公式计算两点间距离（单位：米）
     *
     * @param lat1 点1纬度
     * @param lon1 点1经度
     * @param lat2 点2纬度
     * @param lon2 点2经度
     * @return 两点间距离（米）
     */
    public double calculateDistance(double lat1, double lon1, double lat2, double lon2) {

        // 经纬度转弧度
        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);

        // Haversine公式计算
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLon / 2) * Math.sin(dLon / 2);

        // 计算中心角
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        // 返回弧长（距离）
        return EARTH_RADIUS * c;
    }

    /**
     * 验证考勤配置有效性
     */
    private void validateConfig(HrmAttendanceConfig config) {
        if (config == null) {
            throw new UcodeServiceException(GlobalResultCodeEnum.VALIDATE_FAILED.getCode(),"考勤配置不能为空");
        }

        if (config.getClockInTime() == null || config.getClockOutTime() == null) {
            throw new UcodeServiceException(GlobalResultCodeEnum.VALIDATE_FAILED.getCode(),"上下班时间未配置");
        }

        if (config.getLocationLat() == null || config.getLocationLng() == null) {
            throw new UcodeServiceException(GlobalResultCodeEnum.VALIDATE_FAILED.getCode(),"打卡地点坐标未配置");
        }

        if (config.getAllowRadius() == null || config.getAllowRadius() <= 0) {
            throw new UcodeServiceException(GlobalResultCodeEnum.VALIDATE_FAILED.getCode(),"允许打卡范围未配置或配置错误");
        }

        if (config.getLateThreshold() == null || config.getEarlyLeaveThreshold() == null) {
            throw new UcodeServiceException(GlobalResultCodeEnum.VALIDATE_FAILED.getCode(),"迟到/早退阈值未配置");
        }
    }
}
